home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / pyshared / checkbox / lib / transport.py < prev    next >
Encoding:
Python Source  |  2009-04-27  |  8.6 KB  |  254 lines

  1. #
  2. # This file is part of Checkbox.
  3. #
  4. # Copyright 2008 Canonical Ltd.
  5. #
  6. # Checkbox is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # Checkbox is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with Checkbox.  If not, see <http://www.gnu.org/licenses/>.
  18. #
  19. import logging
  20.  
  21. import os
  22. import stat
  23. import sys
  24. import posixpath
  25.  
  26. import mimetools
  27. import mimetypes
  28. import socket
  29. import httplib
  30. import urllib
  31.  
  32.  
  33. # Build the appropriate socket wrapper for ssl
  34. try:
  35.     # Python 2.6 introduced a better ssl package
  36.     import ssl
  37.     _ssl_wrap_socket = ssl.wrap_socket
  38. except ImportError:
  39.     # Python versions prior to 2.6 don't have ssl and ssl.wrap_socket instead
  40.     # they use httplib.FakeSocket
  41.     def _ssl_wrap_socket(sock, key_file, cert_file):
  42.         ssl_sock = socket.ssl(sock, key_file, cert_file)
  43.         return httplib.FakeSocket(sock, ssl_sock)
  44.  
  45.  
  46. class ProxyHTTPConnection(httplib.HTTPConnection):
  47.  
  48.     _ports = {"http" : httplib.HTTP_PORT, "https" : httplib.HTTPS_PORT}
  49.  
  50.     def request(self, method, url, body=None, headers={}):
  51.         #request is called before connect, so can interpret url and get
  52.         #real host/port to be used to make CONNECT request to proxy
  53.         scheme, rest = urllib.splittype(url)
  54.         if scheme is None:
  55.             raise ValueError, "unknown URL type: %s" % url
  56.         #get host
  57.         host, rest = urllib.splithost(rest)
  58.         #try to get port
  59.         host, port = urllib.splitport(host)
  60.         #if port is not defined try to get from scheme
  61.         if port is None:
  62.             try:
  63.                 port = self._ports[scheme]
  64.             except KeyError:
  65.                 raise ValueError, "unknown protocol for: %s" % url
  66.         self._real_host = host
  67.         self._real_port = port
  68.         httplib.HTTPConnection.request(self, method, url, body, headers)
  69.  
  70.     def connect(self):
  71.         httplib.HTTPConnection.connect(self)
  72.         #send proxy CONNECT request
  73.         self.send("CONNECT %s:%d HTTP/1.0\r\n\r\n" % (self._real_host, self._real_port))
  74.         #expect a HTTP/1.0 200 Connection established
  75.         response = self.response_class(self.sock, strict=self.strict, method=self._method)
  76.         (version, code, message) = response._read_status()
  77.         #probably here we can handle auth requests...
  78.         if code != 200:
  79.             #proxy returned and error, abort connection, and raise exception
  80.             self.close()
  81.             raise socket.error, "Proxy connection failed: %d %s" % (code, message.strip())
  82.         #eat up header block from proxy....
  83.         while True:
  84.             #should not use directly fp probablu
  85.             line = response.fp.readline()
  86.             if line == "\r\n":
  87.                 break
  88.  
  89.  
  90. class ProxyHTTPSConnection(ProxyHTTPConnection):
  91.  
  92.     default_port = httplib.HTTPS_PORT
  93.  
  94.     def __init__(self, host, port=None, key_file=None, cert_file=None, strict=None):
  95.         ProxyHTTPConnection.__init__(self, host, port)
  96.         self.key_file = key_file
  97.         self.cert_file = cert_file
  98.  
  99.     def connect(self):
  100.         ProxyHTTPConnection.connect(self)
  101.         self.sock = _ssl_wrap_socket(self.sock, self.key_file, self.cert_file)
  102.  
  103.  
  104. class HTTPTransport(object):
  105.     """Transport makes a request to exchange message data over HTTP."""
  106.  
  107.     def __init__(self, url):
  108.         self.url = url
  109.  
  110.         proxies = urllib.getproxies()
  111.         self.http_proxy = proxies.get("http")
  112.         self.https_proxy = proxies.get("https")
  113.  
  114.     def _unpack_host_and_port(self, string):
  115.         scheme, rest = urllib.splittype(string)
  116.         host, rest = urllib.splithost(rest)
  117.         host, port = urllib.splitport(host)
  118.         return (host, port)
  119.  
  120.     def _get_connection(self, timeout=0):
  121.         if timeout:
  122.             socket.setdefaulttimeout(timeout)
  123.  
  124.         scheme, rest = urllib.splittype(self.url)
  125.         if scheme == "http":
  126.             if self.http_proxy:
  127.                 host, port = self._unpack_host_and_port(self.http_proxy)
  128.             else:
  129.                 host, port = self._unpack_host_and_port(self.url)
  130.  
  131.             connection = httplib.HTTPConnection(host, port)
  132.         elif scheme == "https":
  133.             if self.https_proxy:
  134.                 host, port = self._unpack_host_and_port(self.https_proxy)
  135.                 connection = ProxyHTTPSConnection(host, port)
  136.             else:
  137.                 host, port = self._unpack_host_and_port(self.url)
  138.                 connection = httplib.HTTPSConnection(host, port)
  139.         else:
  140.             raise Exception, "Unknown URL scheme: %s" % scheme
  141.  
  142.         return connection
  143.  
  144.     def _encode_multipart_formdata(self, fields=[], files=[]):
  145.         boundary = mimetools.choose_boundary()
  146.  
  147.         lines = []
  148.         for (key, value) in fields:
  149.             lines.append("--" + boundary)
  150.             lines.append("Content-Disposition: form-data; name=\"%s\"" % key)
  151.             lines.append("")
  152.             lines.append(value)
  153.  
  154.         for (key, file) in files:
  155.             if hasattr(file, "size"):
  156.                 length = file.size
  157.             else:
  158.                 length = os.fstat(file.fileno())[stat.ST_SIZE]
  159.  
  160.             filename = posixpath.basename(file.name)
  161.             if isinstance(filename, unicode):
  162.                 filename = filename.encode("UTF-8")
  163.  
  164.             lines.append("--" + boundary)
  165.             lines.append("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\""
  166.                 % (key, filename))
  167.             lines.append("Content-Type: %s"
  168.                 % mimetypes.guess_type(filename)[0] or "application/octet-stream")
  169.             lines.append("Content-Length: %s" % length)
  170.             lines.append("")
  171.  
  172.             if hasattr(file, "seek"):
  173.                 file.seek(0)
  174.             lines.append(file.read())
  175.  
  176.         lines.append("--" + boundary + "--")
  177.         lines.append("")
  178.  
  179.         content_type = "multipart/form-data; boundary=%s" % boundary
  180.         body = "\r\n".join(lines)
  181.  
  182.         return content_type, body
  183.  
  184.     def _encode_body(self, body=None):
  185.         fields = []
  186.         files = []
  187.  
  188.         content_type = "application/octet-stream"
  189.         if body is not None and type(body) != str:
  190.             if hasattr(body, "items"):
  191.                 body = body.items()
  192.             else:
  193.                 try:
  194.                     if len(body) and not isinstance(body[0], tuple):
  195.                         raise TypeError
  196.                 except TypeError:
  197.                     ty, va, tb = sys.exc_info()
  198.                     raise TypeError, \
  199.                         "Invalid non-string sequence or mapping", tb
  200.  
  201.             for key, value in body:
  202.                 if hasattr(value, "read"):
  203.                     files.append((key, value))
  204.                 else:
  205.                     fields.append((key, value))
  206.  
  207.             if files:
  208.                 content_type, body = self._encode_multipart_formdata(fields,
  209.                     files)
  210.             elif fields:
  211.                 content_type = "application/x-www-form-urlencoded"
  212.                 body = urllib.urlencode(fields)
  213.             else:
  214.                 body = ""
  215.  
  216.         return content_type, body
  217.  
  218.     def exchange(self, body=None, headers={}, timeout=0):
  219.         headers = dict(headers)
  220.  
  221.         if body is not None:
  222.             method = "POST"
  223.             (content_type, body) = self._encode_body(body)
  224.             if "Content-Type" not in headers:
  225.                 headers["Content-Type"] = content_type
  226.             if "Content-Length" not in headers:
  227.                 headers["Content-Length"] = len(body)
  228.         else:
  229.             method = "GET"
  230.  
  231.         response = None
  232.         connection = self._get_connection(timeout)
  233.  
  234.         try:
  235.             connection.request(method, self.url, body, headers)
  236.         except IOError:
  237.             logging.warning("Can't connect to %s", self.url)
  238.         except socket.error:
  239.             logging.error("Error connecting to %s", self.url)
  240.         except socket.timeout:
  241.             logging.warning("Timeout connecting to %s", self.url)
  242.         else:
  243.             try:
  244.                 response = connection.getresponse()
  245.             except httplib.BadStatusLine:
  246.                 logging.warning("Service unavailable on %s", self.url)
  247.             else:
  248.                 if response.status == httplib.FOUND:
  249.                     # TODO prevent infinite redirect loop
  250.                     self.url = self._get_location_header(response)
  251.                     response = self.exchange(body, headers, timeout)
  252.  
  253.         return response
  254.